package db

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/beego/beego/v2/client/orm"
	"scase.io/application-auto-scaling-service/pkg/common"
	"scase.io/application-auto-scaling-service/pkg/utils/logger"
)

const (
	tableNameEcsInfo      = "ecs_info"
	fieldNameInstanceId   = "instance_id"
	fieldNameInstanceIdIn = "instance_id__in"
	fieldNameGroupId      = "group_id"
	fieldNameActiveTime   = "active_time"
	EcsStateActivating    = "Activating"
	EcsStateActive        = "Active"
	EcsStateError         = "Error"
	EcsStateDeleting      = "Deleting"
	EcsStateDeleted       = "Deleted"
)

type EcsInfo struct {
	InstanceId   string    `orm:"column(instance_id);size(64);pk"`
	Name         string    `orm:"column(name);size(64)"`
	GroupId      string    `orm:"column(group_id);size(64)"`
	FleetId      string    `orm:"column(fleet_id);size(64)"`
	TaskId       string    `orm:"column(task_id);size(64)"`
	JobId        string    `orm:"column(job_id);size(64)"`
	ProjectId    string    `orm:"column(project_id);size(64)"`
	PrivateIP    string    `orm:"column(private_ip);size(64)"`
	PublicIP     string    `orm:"column(public_ip);size(64)"`
	State        string    `orm:"column(state);size(64);"`
	InstanceTags string    `orm:"column(instance_tags);size(1024)"`
	ActiveTime   time.Time `orm:"column(active_time);type(datetime);null"`
	TimeModel
}

func (a *EcsInfo) TableIndex() [][]string {
	return [][]string{
		{"GroupId"},
		{"State"},
	}
}

type ecsInfoTable struct{}

var ecsTable = ecsInfoTable{}

func EcsInfoTable() *ecsInfoTable {
	return &ecsTable
}

func (s *ecsInfoTable) Insert(ecsConfig *EcsInfo) error {
	_, err := ormer.Insert(ecsConfig)
	return err
}

func (s *ecsInfoTable) InsertOrUpdate(ecsConfig *EcsInfo) error {
	_, err := ormer.InsertOrUpdate(ecsConfig)
	return err
}

// 更新状态使用乐观锁
func (s *ecsInfoTable) UpdateStateByOCC(instanceId, newState string) error {
	return ormer.DoTx(func(ctx context.Context, txOrm orm.TxOrmer) error {
		ecsInfo := &EcsInfo{}
		err := txOrm.QueryTable(tableNameEcsInfo).
			Filter(fieldNameInstanceId, instanceId).
			One(ecsInfo)
		if err != nil {
			return err
		}
		num, err := txOrm.QueryTable(tableNameEcsInfo).
			Filter(fieldNameInstanceId, ecsInfo.InstanceId).
			Filter(fieldNameState, ecsInfo.State).Update(
			orm.Params{
				fieldNameState:    newState,
				fieldNameUpdateAt: time.Now().UTC(),
			})
		if err != nil {
			return err
		}
		if num == 0 {
			logger.R.Error("cannot transfer instance [%s] state from %s to %s, because state has been changed",
				instanceId, ecsInfo.State, newState)
			return fmt.Errorf("cannot transfer instance [%s] state from %s to %s, because state has been changed",
				instanceId, ecsInfo.State, newState)
		}
		logger.R.Info("transfer instance [%s] from state %s to new state %s success", instanceId, ecsInfo.State, newState)
		return nil
	})
}
func (s *ecsInfoTable) UpdateActiveTime(instanceId string) error {
	_, err := ormer.QueryTable(tableNameEcsInfo).
		Filter(fieldNameInstanceId, instanceId).
		Update(orm.Params{
			fieldNameActiveTime: time.Now().UTC(),
		})
	return err
}

func (s *ecsInfoTable) CheckExist(instanceId string) bool {
	return ormer.QueryTable(tableNameEcsInfo).
		Filter(fieldNameInstanceId, instanceId).
		Exist()
}

func (s *ecsInfoTable) GetInstanceById(instanceId string) (EcsInfo, error) {
	var ecsConfig EcsInfo
	err := Filters{fieldNameInstanceId: instanceId}.Filter(tableNameEcsInfo).One(&ecsConfig)
	return ecsConfig, err
}

func (s *ecsInfoTable) Update(ecsConfig *EcsInfo) error {
	_, err := ormer.Update(ecsConfig)
	return err
}

func (s *ecsInfoTable) GetAllInstanceIdByGroupId(groupId string, states []string) ([]string, error) {
	var ecsList []EcsInfo
	_, err := ormer.QueryTable(tableNameEcsInfo).Filter(fieldNameGroupId, groupId).
		Filter(fieldNameStateIn, states).
		Filter(fieldNameIsDeleted, notDeletedFlag).OrderBy("create_at").All(&ecsList)
	if err != nil {
		return nil, err
	}
	var ecsIds []string
	for _, ecs := range ecsList {
		ecsIds = append(ecsIds, ecs.InstanceId)
	}

	return ecsIds, nil
}

func (s *ecsInfoTable) ListInstance(groupId, state string, limit, offset int) (int, int, []EcsInfo, error) {
	var ecsList []EcsInfo
	qs := ormer.QueryTable(tableNameEcsInfo)
	condition := orm.NewCondition()
	if state != "" {
		condition = condition.And(fieldNameState, state)
	} else {
		condition = condition.And(fieldNameStateIn, EcsStateActive, EcsStateActivating)
	}
	condition = condition.And(fieldNameGroupId, groupId)
	count, err := qs.SetCond(condition).Limit(limit).Offset(offset * limit).OrderBy(DefaultSort).All(&ecsList)
	if err != nil {
		return 0, 0, nil, err
	}
	total, err := qs.SetCond(condition).Count()
	if err != nil {
		return 0, 0, nil, err
	}
	return int(count), int(total), ecsList, nil
}

// 计算激活中和激活状态下的实例数量作为伸缩组的当前实例数
func (s *ecsInfoTable) CalculateInstanceNumOfGroup(groupId string) (int64, error) {
	var count int64
	count, err := ormer.QueryTable(tableNameEcsInfo).
		Filter(fieldNameGroupId, groupId).
		Filter(fieldNameStateIn, EcsStateActive, EcsStateActivating).Count()
	return count, err
}

func checkState(state string) string {
	if state == "" ||
		state == EcsStateDeleting ||
		state == EcsStateError ||
		state == EcsStateActive ||
		state == EcsStateDeleted {
		return state
	}
	return ""
}

func (s *ecsInfoTable) DeleteDeletingVm(instanceIds []string) error {
	wg := sync.WaitGroup{}
	wg.Add(len(instanceIds))
	for index, instanceId := range instanceIds {
		if index > 0 && index%common.InstanceCountToSleep == 0 {
			time.Sleep(time.Second)
		}
		go func(instance string) {
			_, err := ormer.QueryTable(tableNameEcsInfo).Filter(fieldNameInstanceId, instance).Update(orm.Params{
				fieldNameState:     EcsStateDeleted,
				fieldNameIsDeleted: deletedFlag,
				fieldNameDeleteAt:  time.Now().UTC(),
			})
			if err != nil {
				logger.R.Error("delete instance [%s] error:%+v", instance, err)
			}
		}(instanceId)
	}

	return nil
}

func (s *ecsInfoTable) Delete(instanceId string) error {
	_, err := ormer.QueryTable(tableNameEcsInfo).Filter(fieldNameInstanceId, instanceId).Delete()
	return err
}

// 识别超时未上报的实例
func VerifyZombieInstance(log *logger.FMLogger, workNodeWG *sync.WaitGroup) error {
	defer workNodeWG.Done()
	var zombieInstance []EcsInfo
	q := ormer.QueryTable(tableNameEcsInfo).
		Filter(fieldNameState, EcsStateActive).
		Filter(fieldNameUpdateAtLt, time.Now().UTC().Add(-common.DefaultVMTimeOutDuration*time.Minute).Format(common.TimeLayout))
	zombieCount, err := q.All(&zombieInstance)
	if err != nil {
		return err
	}
	log.Info("finish verify %d zombie Instance, start to transfer state", zombieCount)
	if zombieCount == 0 {
		return nil
	}
	wg := sync.WaitGroup{}
	wg.Add(int(zombieCount))
	for idx, _ := range zombieInstance {
		if idx > 0 && idx%common.InstanceCountToSleep == 0 {
			time.Sleep(common.InstanceCountToSleepSeconds * time.Second)
		}
		go func(index int) {
			instance := zombieInstance[index]
			defer wg.Done()
			if err := ecsTable.UpdateStateByOCC(instance.InstanceId, EcsStateError); err != nil {
				log.Error("update instance %s state to Error fail with error:%+v", instance.InstanceId, err)
				return
			}
			log.Info("instance id %s is zombie instance, last update at %s", instance.InstanceId, instance.UpdateAt)
		}(idx)
	}
	wg.Wait()

	return nil
}

// 用deleting标记正在被缩容，表示已经移出伸缩组
func RemoveInstance(instanceIds []string) error {
	_, err := ormer.QueryTable(tableNameEcsInfo).
		Filter(fieldNameInstanceIdIn, instanceIds).
		Update(orm.Params{
			fieldNameState: EcsStateDeleting,
		})

	return err
}

func CleanUpEcsInfoTable(dayBefore int, log *logger.FMLogger) error {
	logger.R.Info("start to clean ecs info table ...")
	timeLimit := time.Now().UTC().AddDate(0, 0, -dayBefore).Format(common.TimeLayout)
	condition := orm.NewCondition()
	condition = condition.And(fieldNameUpdateAtLt, timeLimit)
	var ecsList = &[]EcsInfo{}
	count, err := ormer.QueryTable(tableNameEcsInfo).SetCond(condition).All(ecsList)
	if err != nil {
		log.Error("get ecs info table before %d days ago data with error:%+v", dayBefore, err)
		return err
	}

	wg := sync.WaitGroup{}
	wg.Add(int(count))
	for index, ecs := range *ecsList {
		if index%common.InstanceCountToSleep == 0 {
			time.Sleep(common.InstanceCountToSleepSeconds)
		}
		go func(instanceId string) {
			defer wg.Done()
			if err := EcsInfoTable().Delete(instanceId); err != nil {
				log.Error("delete instance [%s] error:%+v", instanceId, err)
			}
			log.Info("delete instance [%s] success", instanceId)
		}(ecs.InstanceId)
	}
	wg.Wait()
	return nil
}
